package com.hr;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.springframework.stereotype.Service;

import com.espirit.eap.manager.DepartmentService;
import com.espirit.eap.sa.Employee;
import com.espirit.eap.sa.EmployeeService;
import com.espirit.eap.util.BigDecimalUtil;
import com.espirit.eap.util.DateUtils;
import com.espirit.finance.core.AcctTerm;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.Action;
import com.googlecode.cswish.struts.spring.GenericService;

@Service
public class SalaryTicketService{
	private static final Logger log = Logger.getLogger(SalaryTicketService.class);
	
	
	@Resource
	GenericService genericService;
	
	@Resource
	HrSettingService hrSettingService;
	
	@Resource
	SchedualService schedualService;
	
	@Resource
	EmployeeService employeeService;
	
	@Resource
	LeaveAppItemService leaveAppItemService;
	
	@Resource
	DutyRecordService dutyRecordService;
	
	@Resource
	DepartmentService departmentService;
	
	@Resource
	SalaryDefineService salaryDefineService;
	
	@Resource
	OtItemService otItemService;
	
	protected EntityManager em;
	
	@PersistenceContext(unitName="espirit")
	public void setEntityManager(EntityManager em) {
		this.em = em;
	}
	
	public  PageInfo search(final SalaryTicket model, int start, int limit,
    		String sort, String dir, boolean newSearch, Reference<QLInfo> qlInfoRef,
    		@ParamScope(visible=false, value="#ps")PageElements ps ,boolean fromMenu
    		) {
		if (model != null && model.getEmployee() != null &&  model.getEmployee().getId() != null){
			ps.setElementVisible("model.employee.id", false);
		} else {
			ps.setElementVisible("model.employee.id", true);
		}
		return genericService.search(model, start, limit, sort, dir, true, qlInfoRef);
	}
	
	@Rest(type = Action.SEARCH)
	public PageInfo searchRelatedResult(Salary model){
		if (model == null || model.getId() == null) return null;
		model = genericService.load(model);
		if (model == null) return null;
		
		SalaryTicket salaryTicket = getSalaryTicket(model.getEmployee().getId(), model.getAcctTerm());
		salaryTicket.setId(model.getId());
		
		List data =new ArrayList();
		data.add(salaryTicket);
		
		PageInfo page = new PageInfo(data);
		page.setPageSize(1);
		return page;
	}
	
	public SalaryTicket _update(SalaryTicket model){
		if (model == null || model.getId() == null) return null;
		Salary salary = genericService.load(Salary.class, model.getId());
		if (salary == null) return null;
		
		SalaryTicket salaryTicket = getSalaryTicket(salary.getEmployee().getId(), salary.getAcctTerm());
		return genericService.view(salaryTicket);
	}
	
	public SalaryTicket getCurrentSalaryTicket(Integer empId) {
		AcctTerm currTerm = hrSettingService.getHrSetting().getCurrentAcctTerm();
		return getSalaryTicket(empId, currTerm);
	}
	
	public SalaryTicket getSalaryTicket(Integer empId, AcctTerm acctTerm) {
//		AcctTerm currentAcctTerm = hrSettingService.getHrSetting().getCurrentAcctTerm();
		
		Employee emp = genericService.load(Employee.class, empId);
		SalaryTicket salaryTicket = new SalaryTicket();
		
		salaryTicket.setEmployee(emp);

		Integer deliveryTimes = getTotalDeliveryTimes(emp, acctTerm.getBeginDate(), acctTerm.getEndDate());
		salaryTicket.setDeliveryTimes(deliveryTimes);
		
		BigDecimal faultAmounts = getTotalFaultAmounts(emp, acctTerm.getBeginDate(), acctTerm.getEndDate());
		salaryTicket.setRewardFaultAmount(faultAmounts);
		
		checkDutyRecord(salaryTicket, acctTerm);	
		
		calcAttendencePay(salaryTicket, acctTerm);
		
		
		// dutyrecords can fetch.
//		salaryTicket.setLeaveAppHours(BigDecimal.valueOf(18));
//		SalaryTicketLateItem lateItem = new SalaryTicketLateItem();
//		lateItem.setParent(salaryTicket);
//		lateItem.setMinutes(6);
//		salaryTicket.safeLateItems().add(lateItem);
		
//		salaryTicket = genericService.add(salaryTicket);
		return salaryTicket;
	}
	
	
	private void calcAttendencePay(SalaryTicket salaryTicket, AcctTerm acctTerm){
		if (salaryTicket == null) return ;
		Employee employee = salaryTicket.getEmployee();
		if (employee == null) return ;
		Integer attribute = employee.getAttribute() == null ? Employee.ATTRIBUTE_FIXED_SALARY : employee.getAttribute();
		
		if (Employee.ATTRIBUTE_HOUR_SALARY.equals(attribute)){
			BigDecimal hours = BigDecimalUtil.getValue(salaryTicket.getDutyHours());
			BigDecimal hourSalary = BigDecimalUtil.getValue(employee.getHourSalary());
			BigDecimal salary = hours.multiply(hourSalary);
			salaryTicket.setAttendancePay(salary);
			
			salaryTicket.setHourSalary(hourSalary);
			
		} else {
			SalaryEmployeeDefine salaryEmployeeDefine = salaryDefineService.getSalaryEmployeeDefine(employee.getId(), acctTerm);
//			SchedualDefine schedualDefine = employeeService.getSchedualDefine(employee.getId());
			BigDecimal attendancePay =  BigDecimalUtil.getBigDecimalValue(employee.getFixedSalary());
			
			if (salaryEmployeeDefine != null && salaryEmployeeDefine.getMonthSalaryDays() != null){
				BigDecimal monthDays = BigDecimalUtil.getBigDecimalValue(salaryEmployeeDefine.getMonthSalaryDays());
				if (monthDays != null && monthDays.compareTo(BigDecimal.ZERO) > 0){
					BigDecimal fixedSalary = BigDecimalUtil.getBigDecimalValue(employee.getFixedSalary());
					if (fixedSalary != null){
						BigDecimal daySalary = fixedSalary.divide(monthDays, 6, BigDecimal.ROUND_HALF_DOWN);
						salaryTicket.setDaySalary(daySalary);
						salaryTicket.setHalfDaySalary(daySalary.divide(BigDecimal.valueOf(2), 6, BigDecimal.ROUND_HALF_DOWN));
						BigDecimal dayHours = salaryEmployeeDefine.getDaySalaryHours();
						if (dayHours != null && dayHours.compareTo(BigDecimal.ZERO) > 0){
							BigDecimal hourSalary =  daySalary.divide(dayHours, 6, BigDecimal.ROUND_HALF_DOWN);
							salaryTicket.setHourSalary(hourSalary);
						}
					}
				}
				
				if (monthDays != null){
					BigDecimal schedualDays = salaryTicket.getSchedualDays();
					if (schedualDays != null){
						boolean fullSchedualed = (schedualDays.compareTo(monthDays) >= 0);
						salaryTicket.setFullSchedualed(fullSchedualed);
					}
				}
				
				
				if (!salaryTicket.getFullSchedualed()){
//					BigDecimal schedualDays = salaryTicket.getSchedualDays();
//					if (attendancePay != null && schedualDays != null && monthDays != null){
//						 attendancePay = attendancePay.multiply(schedualDays.divide(monthDays, 6, BigDecimal.ROUND_HALF_DOWN));
//					}
					
					BigDecimal dutyHours = salaryTicket.getDutyHours();
					BigDecimal hourSalary = salaryTicket.getHourSalary();
					if (dutyHours != null && hourSalary != null){
						attendancePay = dutyHours.multiply(hourSalary);
					}
				}
			}
			salaryTicket.setAttendancePay(attendancePay);
		}
	}
	
	
	private void checkDutyRecord(final SalaryTicket salaryTicket, AcctTerm acctTerm) {
		final Employee emp = salaryTicket.getEmployee();
		
		int year = acctTerm.getYear();
		int month = acctTerm.getMonth();
		
		final Date monthStart = DateUtils.generateMonthStart(year, month);
		final Date monthEnd = DateUtils.generateMonthEnd(year, month);
		
		List<SchedualRecord> scheduleRecords = schedualService.getEmployeeSchedualRecords(emp, monthStart, monthEnd);
		List<OtItem> otItems = otItemService.getEmployeeOtItems(emp, monthStart, monthEnd);
		List<LeaveAppItem> leaveAppItems = leaveAppItemService.getEmployeeLeaveAppItems(emp, monthStart, monthEnd);
		
		
//		final SchedualDefine schedualDefine = employeeService.getSchedualDefine(emp.getId());
		
		SalaryEmployeeDefine salaryEmployeeDefine = salaryDefineService.getSalaryEmployeeDefine(emp.getId(), acctTerm);
		
		final List<DayWorkTimes> workTimes = new ArrayList<DayWorkTimes>();
		final List<TimeRange> leaveAppTimeRanges = new ArrayList<TimeRange>();
		final List<RealOtItem> realOtItems = new ArrayList<RealOtItem>();
		int totalDutyMinutes = 0;
		
		for (SchedualRecord sr : scheduleRecords) {
			final Date dutyOn = sr.getDutyOn();
			final String scope = sr.getScope();
			List<TimeRange> dayWorkTimes = schedualService.scope2TimeRanges(dutyOn, scope, TimeRange.MARK_DUTY);
		
			DayWorkTimes dwt = new DayWorkTimes();
			dwt.setDay(dutyOn);
			dwt.setWorkTimes(dayWorkTimes);
			
			workTimes.add(dwt);
		}
		
		final BigDecimal schedualeHours = calcScheduleHours(workTimes);
		salaryTicket.setSchedualHours(schedualeHours);
		
		for(OtItem oti : otItems) {
			Date otTimeFrom = oti.getRealTime();
			Date otTimeTo = DateUtils.addHours(otTimeFrom, oti.getHours().intValue());
			
			TimeRange otWorkTime = new TimeRange(otTimeFrom, otTimeTo);
			otWorkTime.setMarkAndType(TimeRange.MARK_OT, oti.getOtType());
			
			DayWorkTimes dwt = findDayWorkTimes(workTimes, otTimeFrom);
			if (dwt == null) {
				dwt = new DayWorkTimes();
				dwt.setDay(otTimeFrom);
			}
			
			dwt.safeWorkTimes().add(otWorkTime);
		}
		
		for(LeaveAppItem leaveAppItem : leaveAppItems) {
			Date laiTimeFrom = leaveAppItem.getBeginTime();
			Date laiTimeTo = leaveAppItem.getEndTime();
			
			TimeRange tr = new TimeRange(laiTimeFrom, laiTimeTo);
			tr.setMarkAndType(TimeRange.MARK_LEAVEAPP, leaveAppItem.getLeaveType());
			leaveAppTimeRanges.add(tr);
		}
		
		final List<RealLeaveAppItem> realLeaveAppItems = getRealLeaveAppTR(workTimes, leaveAppTimeRanges);//请假时段不需考勤
		calcLeaveAppHours(salaryTicket, realLeaveAppItems);
		
		final Date nextMonthFirstDay = DateUtils.addDays(monthEnd, 1);//次月第一天,考虑到月末排班跨天
		List<DutyRecord> dutyRecords = dutyRecordService.getEmployeeDutyRecords(emp, monthStart, nextMonthFirstDay, null);
		List<PunchInOut> monthPunchIos = new ArrayList<PunchInOut>();
		for (DutyRecord dutyRecord : dutyRecords) {
			if (dutyRecord.getType() == DutyRecord.TYPE_FORGET) {
				addDutyForgetTimes(salaryTicket, 1);
			} else if (dutyRecord.getType() == DutyRecord.TYPE_SUBSTITUTE) {
				addDutySubstituteTimes(salaryTicket, 1);
			}
			
			Date dutyOn = dutyRecord.getDutyTime();
			PunchInOut punchIo = new PunchInOut(dutyOn);
			addOnInexistence(monthPunchIos, punchIo);
		}
		
		
		sortDayWorkTimes(workTimes);
		sortPunchInOut(monthPunchIos);
		
		final HrSetting setting = hrSettingService.getHrSetting();
	    Integer punchInOutType = setting.getPunchInOutType();
		
		if (punchInOutType == null){
			punchInOutType = HrSetting.PUNCH_IN_OUT_TYPE_STRICT;
		}
		
		DayWorkTimes prevDwt = null;
		DayWorkTimes nextDwt = null;
		for (int i = 0; i < workTimes.size(); i++) {
			int nextIndex = i + 1;
			if (nextIndex >= workTimes.size()) {
				nextDwt = null;
			} else {
				nextDwt = workTimes.get(nextIndex);
			}
			DayWorkTimes wt = workTimes.get(i);
			
			CheckResult cr = null;
			if (punchInOutType == HrSetting.PUNCH_IN_OUT_TYPE_LOOSE) {
				cr = looseCheck(prevDwt, wt, nextDwt, monthPunchIos, salaryTicket);
			} else if (punchInOutType == HrSetting.PUNCH_IN_OUT_TYPE_STRICT) {
				cr = strictCheck(prevDwt, wt, nextDwt, monthPunchIos, salaryTicket);
			} else { 
				log.warn("punch in and out check type not found");
			}
			
			if (cr != null) {
				if (cr.isAbsentDay()) {//旷工
					addAbsendDay(salaryTicket, 1);
				} else {
					totalDutyMinutes += cr.getDutyMinutes();
					realOtItems.addAll(cr.safeRealOtItems());
					salaryTicket.safeLateItems().addAll(cr.safeLateItems());
					salaryTicket.safeLeaveEarlyItems().addAll(cr.safeLeaveEarlyItems());
				}
			}			
			
			prevDwt = wt;
		}
		
		calcOtHours(salaryTicket, realOtItems);
		
		final BigDecimal dutyHours = minutes2Hours(totalDutyMinutes);
		salaryTicket.addDutyHours(dutyHours);
		
		if (salaryEmployeeDefine != null && salaryEmployeeDefine.getDaySalaryHours() != null) {
			salaryTicket.setSchedualDays(schedualeHours.divide(salaryEmployeeDefine.getDaySalaryHours() ,2, BigDecimal.ROUND_HALF_DOWN));
			salaryTicket.setDutyDays(dutyHours.divide(salaryEmployeeDefine.getDaySalaryHours() ,2, BigDecimal.ROUND_HALF_DOWN));	
		} else {
//			log.error("salary define is null");
		}
	}

	private void addOnInexistence(final List<PunchInOut> monthPunchIos, PunchInOut punchIo) {
		for (PunchInOut pIo : monthPunchIos) {
			if (pIo.getTime().equals(punchIo.getTime())) {
				return;
			}
		}
		monthPunchIos.add(punchIo);
	}
	
	private void calcOtHours(SalaryTicket salaryTicket,
			List<RealOtItem> realOtItems) {
		int totalOtMinutes = 0;
		for (RealOtItem realOtItem : realOtItems) {
			if (!realOtItem.isExchange()) {
				totalOtMinutes += realOtItem.getRealMinutes();
			}
		}
		
		salaryTicket.setOtHours(minutes2Hours(totalOtMinutes));
	}

	private boolean isPaidLeaveType(Integer leaveAppType) {
		for (int paidLeaveType : hrSettingService.getPaidLeaves()) {
			if (leaveAppType == paidLeaveType) {
				return true;
			}
		}
		return false;
	}
	
	private void addDutySubstituteTimes(SalaryTicket salaryTicket, int times) {
		if (salaryTicket.getDutySubstituteTimes() == null) {
			salaryTicket.setDutySubstituteTimes(times);
		} else {
			salaryTicket.setDutySubstituteTimes(salaryTicket.getDutySubstituteTimes() + times);
		}
	}

	private void addDutyForgetTimes(SalaryTicket salaryTicket, int times) {
		if (salaryTicket.getDutyForgetTimes() == null) {
			salaryTicket.setDutyForgetTimes(times);
		} else {
			salaryTicket.setDutyForgetTimes(salaryTicket.getDutyForgetTimes() + times);
		}
	}

	//TODO
	private CheckResult looseCheck(DayWorkTimes prevDwt, DayWorkTimes dwt, DayWorkTimes nextDwt, List<PunchInOut> monthPunchIos, SalaryTicket st) {
		List<TimeRange> wts = dwt.safeWorkTimes();
		if (wts.isEmpty()) {
			return null;
		}
		
		final CheckResult cr = new CheckResult();
		sortTimeRanges(wts);
		
		
		final TimeRange first = wts.get(0);
		final TimeRange last = wts.get(wts.size() -1);
		//TODO
		PunchInOut punchIn = findPunchIn(null, first.getTimeFrom(), last.getTimeTo(), monthPunchIos);
		PunchInOut punchOut = findPunchOut(first.getTimeFrom(), last.getTimeTo(), null, monthPunchIos);
		final Date realDate = first.getTimeFrom();
		
		if (punchIn != null) {//上班打卡
			cr.safePunchIos().add(punchIn);
			if (punchOut != null) {//下班打卡
				cr.safePunchIos().add(punchIn);
				final int lateMinutes = (int)(punchIn.getMinute() - first.getTimeFromMinute());
				if (lateMinutes > 0 ) {//迟到
					SalaryTicketLateItem lateItem = new SalaryTicketLateItem();
					lateItem.setMinutes(lateMinutes);
					lateItem.setParent(st);
					lateItem.setRealDate(realDate);
					
					cr.safeLateItems().add(lateItem);
				} 
				
				final int leaveEarlyMinutes = (int)(last.getTimeToMinute() - punchOut.getMinute());
				if (leaveEarlyMinutes > 0) {//早退
					SalaryTicketLeaveEarlyItem leaveEarlyItem = new SalaryTicketLeaveEarlyItem();
					leaveEarlyItem.setMinutes(leaveEarlyMinutes);
					leaveEarlyItem.setParent(st);
					leaveEarlyItem.setRealDate(realDate);
					
					cr.safeLeaveEarlyItems().add(leaveEarlyItem);
				} 	
				
				for (TimeRange wt : wts) {
					long beginMinutes = Math.max(wt.getTimeFromMinute(), punchIn.getMinute());
					long endMinutes = Math.min(wt.getTimeToMinute(), punchOut.getMinute());
					final int realDutyMinutes = (int)(endMinutes - beginMinutes);
					
					if (realDutyMinutes > 0) {
						if (TimeRange.MARK_DUTY.equals(wt.getMark())) {//上班
							cr.addDutyMinutes(realDutyMinutes);
						} else if (TimeRange.MARK_OT.equals(wt.getMark())) {//加班
							RealOtItem realOtItem = new RealOtItem();
							realOtItem.setRealMinutes(realDutyMinutes);
							realOtItem.setOtType(wt.getType());
							
							cr.safeRealOtItems().add(realOtItem);
						} else {//错误
							log.error("timerange mark error");
						}
					}
				}
			} else {//下班未打卡
				for (TimeRange wt : wts) {
					SalaryTicketLeaveEarlyItem leaveEarlyItem = new SalaryTicketLeaveEarlyItem();
					leaveEarlyItem.setMinutes(wt.getTimeRangeMinute());
					leaveEarlyItem.setParent(st);
					leaveEarlyItem.setRealDate(realDate);
					
					cr.safeLeaveEarlyItems().add(leaveEarlyItem);
				}
			}
		} else {//上班未打卡
			if (punchOut != null) {//下班打卡
				cr.safePunchIos().add(punchIn);
			}
			
			for (TimeRange wt : wts) {
				SalaryTicketLeaveEarlyItem leaveEarlyItem = new SalaryTicketLeaveEarlyItem();
				leaveEarlyItem.setMinutes(wt.getTimeRangeMinute());
				leaveEarlyItem.setParent(st);
				leaveEarlyItem.setRealDate(realDate);
				
				cr.safeLeaveEarlyItems().add(leaveEarlyItem);
			}
		}
		
		checkAbsentDay(cr);
		return cr;
	}

	private CheckResult strictCheck(DayWorkTimes prevDwt, DayWorkTimes dwt, DayWorkTimes nextDwt, List<PunchInOut> monthPunchIos, SalaryTicket st) {
		if (dwt.safeWorkTimes().isEmpty()) {
			return null;
		}
		
		final CheckResult cr = new CheckResult();
		
		sortTimeRanges(dwt.safeWorkTimes());
		List<SeqTimeRange> seqWts = mergeTimeRanges(dwt.safeWorkTimes());
		
		for (SeqTimeRange seqTr : seqWts) {
//			log.info(DateUtils.format(seqTr.getTimeFrom(), "yyyy-MM-dd HH:mm:ss") + "----" + DateUtils.format(seqTr.getTimeTo(), "yyyy-MM-dd HH:mm:ss"));
			final TimeRange first = seqTr.getFirst();
			final TimeRange last = seqTr.getLast();
//			final int workTimeMinute = (int)(last.getTimeToMinute() - first.getTimeFromMinute());
			
//			PunchInOut punchIn = findPunchInOut(first.getTimeFrom(), monthPunchIos, punchInBeforeMinutes, workTimeMinute);
//			PunchInOut punchOut = findPunchInOut(last.getTimeTo(), monthPunchIos, workTimeMinute, punchOutAfterMinutes);
			//TODO
			PunchInOut punchIn = findPunchIn(null, first.getTimeFrom(), last.getTimeTo(), monthPunchIos);
			PunchInOut punchOut = findPunchOut(first.getTimeFrom(), last.getTimeTo(), null, monthPunchIos);
			
			final Date realDate = first.getTimeFrom();
			
			if (punchIn != null) {//上班打卡
				cr.safePunchIos().add(punchIn);
				if (punchOut != null) {//下班打卡
					cr.safePunchIos().add(punchOut);
					final int lateMinutes = (int)(punchIn.getMinute() - seqTr.getTimeFromMinute());
					if (lateMinutes > 0 ) {//迟到
						SalaryTicketLateItem lateItem = new SalaryTicketLateItem();
						lateItem.setMinutes(lateMinutes);
						lateItem.setParent(st);
						lateItem.setRealDate(realDate);
						
						cr.safeLateItems().add(lateItem);
					} 
					
					final int leaveEarlyMinutes = (int)(seqTr.getTimeToMinute() - punchOut.getMinute());
					if (leaveEarlyMinutes > 0) {//早退
						SalaryTicketLeaveEarlyItem leaveEarlyItem = new SalaryTicketLeaveEarlyItem();
						leaveEarlyItem.setMinutes(leaveEarlyMinutes);
						leaveEarlyItem.setParent(st);
						leaveEarlyItem.setRealDate(realDate);
						
						cr.safeLeaveEarlyItems().add(leaveEarlyItem);
					} 
					
					for (TimeRange childTr : seqTr.safeChilds()) {
						long beginMinutes = Math.max(childTr.getTimeFromMinute(), punchIn.getMinute());
						long endMinutes = Math.min(childTr.getTimeToMinute(), punchOut.getMinute());
						final int realDutyMinutes = (int)(endMinutes - beginMinutes);
						
						if (realDutyMinutes > 0) {
							if (TimeRange.MARK_DUTY.equals(childTr.getMark())) {//上班
								cr.addDutyMinutes(realDutyMinutes);
							} else if (TimeRange.MARK_OT.equals(childTr.getMark())) {//加班
								RealOtItem realOtItem = new RealOtItem();
								realOtItem.setRealMinutes(realDutyMinutes);
								realOtItem.setOtType(childTr.getType());
								
								cr.safeRealOtItems().add(realOtItem);
							} else {//错误
								log.error("timerange mark error");
							}
						}
					}
				} else {//下班未打卡
					SalaryTicketLeaveEarlyItem leaveEarlyItem = new SalaryTicketLeaveEarlyItem();
					leaveEarlyItem.setMinutes(seqTr.getTimeRangeMinute());
					leaveEarlyItem.setParent(st);
					leaveEarlyItem.setRealDate(realDate);
					cr.safeLeaveEarlyItems().add(leaveEarlyItem);
				}
			} else {//上班未打卡
				if (punchOut != null) {//下班打卡
					cr.safePunchIos().add(punchOut);
				} 

				SalaryTicketLateItem lateItem = new SalaryTicketLateItem();
				lateItem.setMinutes(seqTr.getTimeRangeMinute());
				lateItem.setParent(st);
				lateItem.setRealDate(realDate);
				
				cr.safeLateItems().add(lateItem);
			}
		}
		
		checkAbsentDay(cr);
		return cr;
	}

	
	private void checkAbsentDay(CheckResult cr) {
		if (cr.safePunchIos().isEmpty()) {//一整天没有打卡记录,视为旷工
			cr.setAbsentDay(true);
		}
	}

	private void sortPunchInOut(List<PunchInOut> monthPunchIos) {
		Collections.sort(monthPunchIos, new Comparator<PunchInOut>() {
			@Override
			public int compare(PunchInOut o1, PunchInOut o2) {
				return o1.getTime().compareTo(o2.getTime());
			}
		});		
	}

	private void sortDayWorkTimes(List<DayWorkTimes> workTimes) {
		Collections.sort(workTimes, new Comparator<DayWorkTimes>() {
			@Override
			public int compare(DayWorkTimes dwt1, DayWorkTimes dwt2) {
				return dwt1.getDay().compareTo(dwt2.getDay());
			}
		});
	}

	private DayWorkTimes findDayWorkTimes(List<DayWorkTimes> dayWorkTimes, Date day) {
		for (DayWorkTimes dwt : dayWorkTimes) {
			if (DateUtils.sameDay(dwt.getDay(), day)) {
				return dwt;
			}
		}
		
		return null;
	}
	
	
	private BigDecimal calcScheduleHours(List<DayWorkTimes> workTimes) {
		int totalScheduleMinutes = 0;
		for (DayWorkTimes dwt : workTimes) {
			for (TimeRange workTime : dwt.safeWorkTimes()) {
				totalScheduleMinutes += workTime.getTimeRangeMinute();
			}
		}
		
		return minutes2Hours(totalScheduleMinutes);
	}

	private void sortTimeRanges(final List<TimeRange> workTimeRanges) {
		Collections.sort(workTimeRanges, new Comparator<TimeRange>() {
			@Override
			public int compare(TimeRange o1, TimeRange o2) {
				return o1.getTimeFrom().compareTo(o2.getTimeFrom());
			}
		});
	}

	public static BigDecimal minutes2Hours(int minutes) {
		return new BigDecimal(minutes).divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP);
	}

	private void addAbsendDay(final SalaryTicket salaryTicket, int absentTimes) {
		if (salaryTicket.getAbsentDays() == null) {
			salaryTicket.setAbsentDays(absentTimes);
		} else {
			salaryTicket.setAbsentDays(salaryTicket.getAbsentDays() + absentTimes);
		}
	}
	
	private PunchInOut findPunchIn(Date prevPunchOutTime, Date punchInTime, Date punchOutTime, List<PunchInOut> monthPunchIos) {
		if (prevPunchOutTime == null) {
			prevPunchOutTime = DateUtils.generateDayStart(punchInTime);
		}
		
		PunchInOut punchIn1 = null;
		PunchInOut punchIn2 = null;
		//上班时间点之前打卡
		List<PunchInOut> beforePunchIns = findPunchInOuts(monthPunchIos, prevPunchOutTime, punchInTime);
		if (!beforePunchIns.isEmpty()) {
			punchIn1 = beforePunchIns.get(beforePunchIns.size() - 1);//获取离上班最近的记录
		}
		
		//上班时间点之后打卡
		List<PunchInOut> afterPunchIns = findPunchInOuts(monthPunchIos, punchInTime, punchOutTime);
		if (!afterPunchIns.isEmpty()) {
			punchIn2 = afterPunchIns.get(0);
		}
		
		if (punchIn1 != null && punchIn2 != null) {//选择与考勤点距离近的作为打卡记录
			long minute = DateUtils.millsecond2Minute(punchInTime);
			long minute1 = Math.abs(minute - punchIn1.getMinute());
			long minute2 = Math.abs(punchIn2.getMinute() - minute);
			
			if (minute1 <= minute2) {
				monthPunchIos.remove(punchIn1);
				return punchIn1;
			} else {
				monthPunchIos.remove(punchIn2);
				return punchIn2;
			}
		} 
		
		if (punchIn1 != null) {
			monthPunchIos.remove(punchIn1);
			return punchIn1;
		} 
		
		if (punchIn2 != null) {
			monthPunchIos.remove(punchIn2);
			return punchIn2;
		}
		
		
		return null;
	}
	
	private PunchInOut findPunchOut(Date punchInTime, Date punchOutTime, Date nextPunchIn, List<PunchInOut> monthPunchIos) {
		if (nextPunchIn == null) {
			nextPunchIn = DateUtils.generateDayEnd(punchOutTime);
		}
		
		PunchInOut punchOut1 = null;
		PunchInOut punchOut2 = null;
		
		//下班时间点之后打卡
		List<PunchInOut> afterPunchOuts = findPunchInOuts(monthPunchIos, punchOutTime, nextPunchIn);
		if (!afterPunchOuts.isEmpty()) {
			punchOut1 = afterPunchOuts.get(0);
		}
		
		//下班时间点之前打卡
		List<PunchInOut> beforePunchOuts = findPunchInOuts(monthPunchIos, punchInTime, punchOutTime);
		if (!beforePunchOuts.isEmpty()) {
			punchOut2 = beforePunchOuts.get(beforePunchOuts.size() - 1);//获取离下班最近的打卡记录
		}
		
		if (punchOut1 != null && punchOut2 != null) {//选择与考勤点距离近的作为打卡记录
			long minute = DateUtils.millsecond2Minute(punchOutTime);
			long minute1 = Math.abs(punchOut1.getMinute() - minute);
			long minute2 = Math.abs(minute - punchOut2.getMinute());
			
			if (minute1 <= minute2) {
				monthPunchIos.remove(punchOut1);
				return punchOut1;
			} else {
				monthPunchIos.remove(punchOut2);
				return punchOut2;
			}
		} 
		
		if (punchOut1 != null) {
			monthPunchIos.remove(punchOut1);
			return punchOut1;
		} 
		
		if (punchOut2 != null) {
			monthPunchIos.remove(punchOut2);
			return punchOut2;
		}
		
		return null;
	}
	
	
	
	private PunchInOut findPunchInOut(Date timePoint, List<PunchInOut> monthPunchIos, int beforeMinutes, int afterMinutes) {
		Date punchOutFrom = DateUtils.addMinutes(timePoint, -beforeMinutes);
		Date punchOutTo =DateUtils.addMinutes(timePoint, afterMinutes);
		
		List<PunchInOut> punchOuts = findPunchInOuts(monthPunchIos, punchOutFrom, punchOutTo);
		if (punchOuts.isEmpty()) {
			return null;
		} else {
			PunchInOut punchInOut = punchOuts.get(0);
			
			for (PunchInOut pio : punchOuts) {//移除重复打卡
				if (punchInOut.getMinute() == pio.getMinute()) {
					monthPunchIos.remove(pio);
				}
			}
			
			return punchInOut;
		}
	}

	public static List<SeqTimeRange> mergeTimeRanges(List<TimeRange> timeRanges) {
		List<SeqTimeRange> ret = new ArrayList<SeqTimeRange>();
		
		Iterator<TimeRange> it = timeRanges.iterator();
		
		SeqTimeRange trc = null;
		while(it.hasNext()) {
			TimeRange tr = it.next();
			if (trc == null) {
				trc = new SeqTimeRange(tr);
			} else {
				TimeRange last = trc.getLast();
				if (last.getTimeToMinute() == tr.getTimeFromMinute()) {
					trc.addLastChild(tr);
				} else {
					ret.add(trc);
					trc = new SeqTimeRange(tr);
				}
			}
		}
		
		if (trc != null) {
			ret.add(trc);
		}
		
		return ret;
	}

	private void calcLeaveAppHours(final SalaryTicket salaryTicket,
			final List<RealLeaveAppItem> leaveAppItems) {
		BigDecimal totalLeaveAppHours = new BigDecimal(0);
		for (RealLeaveAppItem leaveAppItem : leaveAppItems) {
			BigDecimal leaveAppHours = minutes2Hours(leaveAppItem.getRealMinutes());
			
			Integer leaveAppType = leaveAppItem.getLeaveAppType();
			
			BigDecimal hours = salaryTicket.safeLeaveApps().get(leaveAppType);
			if (hours == null) {
				salaryTicket.safeLeaveApps().put(leaveAppType.toString(), leaveAppHours);
			} else {
				salaryTicket.safeLeaveApps().put(leaveAppType.toString(), hours.add(leaveAppHours));
			}
			
			if (isPaidLeaveType(leaveAppItem.getLeaveAppType())) {//带薪休假，纳入上班时间
				salaryTicket.addDutyHours(leaveAppHours);
			} else {//非带薪假期，纳入请假时间
				totalLeaveAppHours = totalLeaveAppHours.add(leaveAppHours);
			}
		}
		salaryTicket.setLeaveAppHours(totalLeaveAppHours);
	}
	
//	public static BigDecimal otMinutes2Hour(int otMinutes) {
//		BigDecimal hours = new BigDecimal(otMinutes / 60);
//		
//		long remainMinutes = otMinutes % 60;
//		if (remainMinutes >= 30) {
//			hours = hours.add(new BigDecimal(0.5));
//		} 
//		
//		return hours;
//	}
	
	private List<PunchInOut> findPunchInOuts(List<PunchInOut> monthPunchIos,
			Date timeFrom, Date timeTo) {
		List<PunchInOut> ret = new ArrayList<PunchInOut>();
		
		long minuteFrom = DateUtils.millsecond2Minute(timeFrom);
		long minuteTo = DateUtils.millsecond2Minute(timeTo);
		
		for (PunchInOut punchIo : monthPunchIos) {
			long minute = punchIo.getMinute();
			if (minute >= minuteFrom && minute <= minuteTo) {
				ret.add(punchIo);
			}
		}
		return ret;
	}

	
	private List<RealLeaveAppItem> getRealLeaveAppTR(List<DayWorkTimes> workTimes, List<TimeRange> leaveAppTimeRanges) {
		List<RealLeaveAppItem> ret = new ArrayList<RealLeaveAppItem>();
		for (DayWorkTimes dwt : workTimes) {
			List<TimeRange> wts = dwt.safeWorkTimes();
			List<RealLeaveAppItem> realLeaveAppItems = getRealLeaveAppTR0(wts, leaveAppTimeRanges);
			
			ret.addAll(realLeaveAppItems);
		}
		
		return ret;
	}
	
	private List<RealLeaveAppItem> getRealLeaveAppTR0(List<TimeRange> workTimes, List<TimeRange> leaveAppTimeRanges) {
		List<RealLeaveAppItem> leaveAppItems = new ArrayList<RealLeaveAppItem>();
		List<TimeRange> temp = new ArrayList<TimeRange>();
		
		for (TimeRange leaveAppTr : leaveAppTimeRanges) {
			temp.clear();
			temp.addAll(workTimes);

			for (TimeRange tr : temp) {
				if (leaveAppTr.isIntersect(tr)) {//工作时段与请假时段相交
					workTimes.remove(tr);
					
					long leaveAppTimeFrom = Math.max(leaveAppTr.getTimeFromMinute(), tr.getTimeFromMinute());
					long leaveAppTimeTo = Math.min(leaveAppTr.getTimeToMinute(), tr.getTimeToMinute());
					int leaveAppTimeMinutes = (int)(leaveAppTimeTo - leaveAppTimeFrom);
					if (leaveAppTimeMinutes > 0) {
						RealLeaveAppItem leaveAppItem = new RealLeaveAppItem();
						leaveAppItem.setRealMinutes(leaveAppTimeMinutes);
						leaveAppItem.setLeaveAppType(leaveAppTr.getType());
						
						leaveAppItems.add(leaveAppItem);
					}
					
					if (tr.getTimeFromMinute() < leaveAppTr.getTimeFromMinute()) {
						Date timeFrom = tr.getTimeFrom();
						Date timeTo = leaveAppTr.getTimeFrom();
						
						TimeRange newTr = new TimeRange(timeFrom, timeTo);
						newTr.setMarkAndType(tr.getMark(), tr.getType());
						workTimes.add(newTr);
					}
					
					if (tr.getTimeToMinute() > leaveAppTr.getTimeToMinute()) {
						Date timeFrom = leaveAppTr.getTimeTo();
						Date timeTo = tr.getTimeTo();
						
						TimeRange newTr = new TimeRange(timeFrom, timeTo);
						newTr.setMarkAndType(tr.getMark(), tr.getType());
						workTimes.add(newTr);
					}
				}
			}
		}
		
		return leaveAppItems;
	}

	public BigDecimal getTotalFaultAmounts(Employee emp, Date beginDate, Date endDate) {
		String ql = String.format("select sum(coalesce(a.rewardAmount, 0) - coalesce(a.punishAmount, 0)) from %s a where a.employee.id = %s and a.realDate between '%s' and '%s'"
				, RewardFaultItem.class.getName(), emp.getId(), beginDate, endDate);
		return getTotalAmountValue(ql);
	}

	public Integer getTotalDeliveryTimes(Employee emp, Date beginDate, Date endDate) {
//		String ql = "select sum(a.times) from " + DeliveryTimeItem.class.getName() + " a where a.employee.id = ? and a.realDate between ? and ?";
//		genericService.searchByQl(new QLInfo(ql, true, emp.getId(), beginDate, endDate));
		String ql = String.format("select sum(a.times) from %s a where a.employee.id = %s and a.realDate between '%s' and '%s'"
									, DeliveryTimeItem.class.getName(), emp.getId(), beginDate, endDate);
		return (Integer) getTotalValue(ql);
	}
	
	private Object getTotalValue(String ql){
		Long totalQty = (Long) getSession().createQuery(ql).uniqueResult();
		return (totalQty == null ? 0 : totalQty.intValue());
	}

	private BigDecimal getTotalAmountValue(String ql){
		Object totalQty =  getSession().createQuery(ql).uniqueResult();
		return BigDecimalUtil.getBigDecimalValue(totalQty);
	}

	public Session getSession(){
		return (Session)em.getDelegate();
	}

	
}
